from lxml import etree
from jinja2 import Environment, FileSystemLoader
import os
import logging

# 设置日志级别
logging.basicConfig(level="INFO")


class Generator(object):

    def __init__(self, mode_file="./My.aquila", template="template.java", output_dir="src"):
        # aquila模型位置
        self.aquila_file = mode_file
        env = Environment(loader=FileSystemLoader('.'))
        # 在环境中注册过滤器
        env.filters['upper_first'] = self.upper_first
        # JAVA 代码模板
        self.template = env.get_template(template)
        # 输出文件夹
        self.output_dir = output_dir
        # 解析出的类列表
        self.classes = []
        # 默认类型
        self.default_return_type = "void"
        self.default_attribute_type = "String"

    def get_type(self, type_, default):
        """获取自定义类型"""
        if len(type_) == 0:
            return default
        return self.classes[int(type_[0].split('.')[1])].xpath("@name")[0]

    @staticmethod
    def upper_first(string: str):
        """将第一个字母变成大写"""
        return string[0].upper() + string[1:]

    def parse_operations(self, class_: etree.Element) -> list:
        """解析类的方法"""
        operations = class_.xpath("operations")
        res = []
        for operation in operations:
            name = operation.xpath("@name")[0]
            return_type = self.get_type(operation.xpath("@returnType"), self.default_return_type)
            params = []
            # 获取参数列表
            for para in operation.xpath("parameters"):
                para_type = self.get_type(para.xpath("@parameterType"), self.default_attribute_type)
                params.append(f"{para_type} {para.xpath('@name')[0]}")
            res.append({
                "name": name,
                "return_type": return_type,
                # 模板生成参数列表太为麻烦， 所以此处直接生成好传进去
                "param_list": ", ".join(params)
            })
        return res

    def parse_attribute(self, class_: etree.Element) -> list:
        """解析属性"""
        attributes = class_.xpath("structuralFeatures")
        res = []
        for attribute in attributes:
            name = attribute.xpath("@name")[0]
            attribute_type = self.get_type(attribute.xpath("@referenceType"), self.default_attribute_type)
            # upperBound大于1时属性值为列表
            if int(attribute.xpath("@upperBound")[0]) > 1:
                attribute_type = attribute_type + "[]"
            res.append({
                "type": attribute_type,
                "name": name
            })
        return res

    def parse_dependencies(self, class_: etree.Element):
        """解析依赖"""
        dependencies = class_.xpath("dependencies")
        res = []
        for dependence in dependencies:
            res.append({
                "class": self.get_type(dependence.xpath("@target"), self.default_attribute_type),
                "kind": dependence.xpath("@kind")[0]
            })
        return res

    def parse_aquila_model(self):
        """解析aquila模型"""
        for c in self.classes:
            name = c.xpath("@name")[0]
            abstract = c.xpath("@abstract")
            # 是否为抽象类
            abstract = True if "true" in abstract else False
            super_types = c.xpath("@superTypes")
            if super_types:
                if str(super_types).count("//") > 1:
                    # java不考虑多继承，默认直接忽略
                    super_types = False
                else:
                    super_types = self.classes[int(super_types[0].split('.')[1])].xpath("@name")[0]
            else:
                super_types = False
            try:
                if not os.path.exists(self.output_dir):
                    os.makedirs(self.output_dir)
                with open(os.path.join(self.output_dir, f"{name}.java"), 'w') as f:
                    f.write(self.template.render({
                        "name": name,
                        "abstract": abstract,
                        "super_types": super_types,
                        "attributes": self.parse_attribute(c),
                        "operations": self.parse_operations(c),
                        "dependencies": self.parse_dependencies(c)
                    }))
                logging.info(f"Successfully to generate class {name}")
            except Exception as e:
                logging.error(e)

    def load_aquila_model(self):
        """加载aquila模型"""
        try:
            with open(self.aquila_file, 'r') as f:
                xml = etree.XML(f.read().encode())
            classes = xml.xpath("//elements")
            return classes
        except OSError:
            return False

    def run(self):
        classes = self.load_aquila_model()
        if isinstance(classes, list):
            self.classes = classes
            logging.info("Successfully to load aquila model")
            self.parse_aquila_model()
        else:
            logging.warning("Failed to load aquila model")
